﻿#region License, Terms and Author(s)
//
// BugTraqPie - IBugTraqProvider for (Iron)Python
// Copyright (c) 2009 Atif Aziz. All rights reserved.
//
//  Author(s):
//
//      Atif Aziz, http://www.raboof.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

namespace BugTraqPie
{
    #region Imports

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;

    using PythonEngine = IronPython.Hosting.PythonEngine;
    using Ops = IronPython.Runtime.Operations.Ops;

    #endregion

    [ComVisible(true)]
    [Guid("11AADE4B-D27C-48c9-BAC9-E4AC0F74A3C5")]
    [ClassInterface(ClassInterfaceType.None)]
    public sealed class Plugin : Imports.IBugTraqProvider2, IDisposable
    {
        private PythonEngine _engine;
        private IBugTraqProvider _provider;

        private static string _assemblyDirectoryPath;

        private static string AssemblyDirectoryPath
        {
            get
            {
                if (_assemblyDirectoryPath == null)
                {
                    var codeBase = new Uri(typeof(Plugin).Assembly.CodeBase);
                    _assemblyDirectoryPath = Path.GetDirectoryName(codeBase.LocalPath);
                }
                return _assemblyDirectoryPath;
            }
        }

        private string Parameters { get; set; }

        private IBugTraqProvider GetProvider()
        {
            return GetProvider(null);
        }

        private IBugTraqProvider GetProvider(string parameters)
        {
            if (_provider == null)
            {
                if (string.IsNullOrEmpty(parameters))
                    throw new InvalidOperationException();

                CreateProvider(parameters);
                Debug.Assert(_provider != null);
            }

            return _provider;
        }

        private void CreateProvider(string parameters)
        {
            Debug.Assert(parameters != null);

            //
            // Split script name from actual parameters, which
            // are delimited by a colon.
            //

            var pair = parameters.Split(new[] { ':' }, 2);
            var script = pair.First();
            parameters = pair.Skip(1).SingleOrDefault() ?? string.Empty;

            if (script.Length == 0)
                throw new Exception("Script name missing from parameters.");

            //
            // Find the script by starting at the current directory
            // and checking all the way up the path ancestory.
            //

            var searchPaths = PiePath.GetPathThenAncestory(Environment.CurrentDirectory);
            var path = PiePath.FindFile(script, searchPaths);
            if (path == null)
                throw new Exception("Script not found.");

            //
            // Create the engine, default module and execute the script.
            //

            var engine = new PythonEngine();
            engine.AddToPath(AssemblyDirectoryPath);
            engine.DefaultModule = engine.CreateModule();
            engine.ExecuteFile(path);

            //
            // Get a class by the name BugTraqProvider.
            //
            
            object clazz;
            if (!engine.Globals.TryGetValue("BugTraqProvider", out clazz))
                throw new Exception("BugTraqProvider not found in script.");

            //
            // Create an instance of the BugTraqProvider class and make
            // sure it implements IBugTraqProvider.
            //

            var obj = Ops.Call(clazz);
            if (obj == null)
                throw new Exception("Unexpected BugTraqProvider implementation.");

            var provider = obj as IBugTraqProvider;
            if (provider == null)
                throw new Exception(string.Format("BugTraqProvider found in script does not implement {0}.", typeof(IBugTraqProvider)));

            //
            // Commit state.
            //
            
            _engine = engine;
            _provider = provider;
            Parameters = parameters;
        }

        public bool ValidateParameters(IntPtr hParentWnd, string parameters)
        {
            return GetProvider(parameters).ValidateParameters(new ValidateParametersParams
            {
                ParentWindow = hParentWnd,
                Parameters = Parameters, 
            });
        }

        public string GetLinkText(IntPtr hParentWnd, string parameters)
        {
            return GetProvider(parameters).GetLinkText(new GetLinkTextParams
            {
                ParentWindow = hParentWnd,
                Parameters = Parameters,
            });
        }

        public string GetCommitMessage(IntPtr hParentWnd, string parameters, string commonRoot, string[] pathList, string originalMessage)
        {
            return GetProvider(parameters).GetCommitMessage(new GetCommitMessageParams
            {
                ParentWindow = hParentWnd,
                Parameters = Parameters,
                CommonRoot = commonRoot,
                PathList = new ReadOnlyCollection<string>(pathList),
                OriginalMessage = originalMessage,
            });
        }

        public string GetCommitMessage2(IntPtr hParentWnd, string parameters, string commonURL, string commonRoot, string[] pathList, string originalMessage, string bugID, out string bugIDOut, out string[] revPropNames, out string[] revPropValues)
        {
            var args = new GetCommitMessageParams
            {
                ParentWindow = hParentWnd,
                Parameters = Parameters,
                CommonUrl = commonURL,
                CommonRoot = commonRoot,
                PathList = new ReadOnlyCollection<string>(pathList),
                OriginalMessage = originalMessage,
                BugId = bugID,
                RevProps = new Dictionary<string, string>(),
            };
            
            var result = GetProvider(parameters).GetCommitMessage2(args);
            
            bugIDOut = args.BugId;
            
            revPropNames = null;
            revPropValues = null;
            var props = args.RevProps;
            if (props.Any())
            {
                revPropNames = props.Keys.ToArray();
                revPropValues = props.Values.ToArray();
            }
            
            return result;
        }

        public string CheckCommit(IntPtr hParentWnd, string parameters, string commonURL, string commonRoot, string[] pathList, string commitMessage)
        {
            return GetProvider(parameters).CheckCommit(new CheckCommitParams
            {
                ParentWindow = hParentWnd,
                Parameters = Parameters,
                CommonUrl = commonURL,
                CommonRoot = commonRoot,
                PathList = new ReadOnlyCollection<string>(pathList),
                CommitMessage = commitMessage,
            });
        }

        public string OnCommitFinished(IntPtr hParentWnd, string commonRoot, string[] pathList, string logMessage, int revision)
        {
            return GetProvider().OnCommitFinished(new OnCommitFinishedParams
            {
                ParentWindow = hParentWnd,
                CommonRoot = commonRoot,
                PathList = new ReadOnlyCollection<string>(pathList),
                LogMessage = logMessage,
                Revision = revision,
            });
        }

        public bool HasOptions()
        {
            return GetProvider().HasOptions(new HasOptionsParams());
        }

        public string ShowOptionsDialog(IntPtr hParentWnd, string parameters)
        {
            return GetProvider(parameters).ShowOptionsDialog(new ShowOptionsDialogParams
            {
                ParentWindow = hParentWnd,
                Parameters = Parameters,
            });
        }

        public void Dispose()
        {
            TryDispose(ref _engine);
            TryDispose(ref _provider);
        }

        private static void TryDispose<T>(ref T obj) where T : class
        {
            if (obj == null)
                return;

            var disposable = obj as IDisposable;
            obj = null;

            if (disposable == null)
                return;
            
            try
            {
                disposable.Dispose();
            }
            catch (Exception e)
            {
                Trace.TraceError(e.ToString());
            }
        }
    }
}
